﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Reflection;
using System.Windows.Input;
using DG.Common.EF.Reflection;
using DG.Common.Patterns.Commands;
using DG.Common.Patterns.MVVM;
using DG.Common.Reflection;

namespace DG.Common.EF.WPF
{
    public class EntityListViewerModel : ViewModelBase
    {
        private object _selectedEntity;

        private IDbContext _context;
        private Type _type;
        private ContextConceptualInfos _conceptualInfos;
        private Func<IDbContext> _ctxFactory;
        private object[] _entitiesList;
        private DataRowView _selectedDataTableItem;
        private DataTable _dataTableEntityList;
        private ListFilter _listFilter;

        public IDbSet Set
        {
            get { return Context.Set(Type); }
        }

        public DataRowView SelectedDataTableItem
        {
            get { return _selectedDataTableItem; }
            set
            {
                if (Equals(value, _selectedDataTableItem)) return;
                _selectedDataTableItem = value;
                OnPropertyChanged("SelectedDataTableItem");

                if (value == null)
                {
                    SelectedEntity = null;
                }
                else
                {
                    var propKeys = ConceptualInfos.EntitiesConceptualInfos[Type].Keys;
                    var keys = propKeys.Select(k => value.Row[k.Name]).ToArray();
                    SelectedEntity = Set.Find(keys);    
                }
                
            }
        }

        public object SelectedEntity
        {
            get { return _selectedEntity; }
            private set
            {
                if (Equals(value, _selectedEntity)) return;
                _selectedEntity = value;
                OnPropertyChanged("SelectedEntity");
            }
        }

        public IDbContext Context
        {
            get { return _context; }
            private set
            {
                if (Equals(value, _context)) return;
                _context = value;
                OnPropertyChanged("Context");
            }
        }
        public Type Type
        {
            get { return _type; }
            private set
            {
                if (Equals(value, _type)) return;
                _type = value;
                OnPropertyChanged("Type");
            }
        }

        public IEnumerable<PropertyInfo> ReadOnlyProperties { get; set; }
        public IEnumerable<PropertyInfo> HideProperties { get; set; }

        public ContextConceptualInfos ConceptualInfos
        {
            get { return _conceptualInfos; }
            private set
            {
                if (Equals(value, _conceptualInfos)) return;
                _conceptualInfos = value;
                OnPropertyChanged("ConceptualInfos");
            }
        }

        public ListFilter ListFilter
        {
            get { return _listFilter; }
            set
            {
                if (Equals(value, _listFilter)) return;
                _listFilter = value;
                OnPropertyChanged("ListFilter");
            }
        }

        public object[] EntitiesList
        {
            get { return _entitiesList; }
            set
            {
                if (Equals(value, _entitiesList)) return;
                _entitiesList = value;
                OnPropertyChanged("EntitiesList");

                var typeInfos = ConceptualInfos.EntitiesConceptualInfos[Type];
                var props = typeInfos.NavigationProperties.Union(typeInfos.PrimitiveProperties)
                    .OrderByDescending(p=> typeInfos.PropertiesMeta[p].IsKey)
                    .ThenBy(p=> p.Name).Distinct().ToArray();
                
                DataTableEntityList = EntitiesList.ToDataTable(props);
            }
        }

        public DataTable DataTableEntityList
        {
            get { return _dataTableEntityList; }
            set
            {
                if (Equals(value, _dataTableEntityList)) return;
                _dataTableEntityList = value;
                OnPropertyChanged("DataTableEntityList");
            }
        }

        public EntityListViewerModel(Func<IDbContext> ctxFactory, Type entityType,
            IEnumerable<PropertyInfo> readOnlyProperties = null,
            IEnumerable<PropertyInfo> hideProperties = null
            , Action<Action<IActionResult>> customInserir = null, 
            Action<object, Action<IActionResult>> customModificar = null, 
            Action<object, IDbContext> customDelete = null, 
            Func<IDbSet, IDbContext, IQueryable> customFiltrar = null)
        {
            _ctxFactory = ctxFactory;
            Type = entityType;
            ReadOnlyProperties = readOnlyProperties;
            HideProperties = hideProperties;

            CustomDelete = customDelete;
            CustomFiltrar = customFiltrar;
            CustomInserir = customInserir;
            CustomModificar = customModificar;

            IncluirVisivel = true;
            ModificarVisivel = true;
            DeletarVisivel = true;

            ReloadEntities();
        }

        
        private void ReloadEntities()
        {
            Context = _ctxFactory.Invoke();

            ConceptualInfos = ContextConceptualInfosFactory.GetContextEdmxForContext(Context.Context);

            var propertiesMeta = ConceptualInfos.EntitiesConceptualInfos[Type].PropertiesMeta;

            var listFilter = propertiesMeta.Where(p =>
            {
                var brows = p.Value.Property.GetCustomAttributes(typeof(BrowsableAttribute), true).FirstOrDefault() as BrowsableAttribute;
                if (brows != null && !brows.Browsable) return false;

                var filt = p.Value.Property.GetCustomAttributes(typeof(FilteredAtributte), true).FirstOrDefault() as FilteredAtributte;
                if (filt != null && !filt.ShowOnFilter) return false;

                return true;
            })
                .Select(p => new EntityViewerPropertyFilter(Context, p.Value)).ToArray();

            ListFilter = new ListFilter(listFilter);
            
            Set.Load();

            FilterEntities();
        }


        private void FilterEntities()
        {
            IEnumerable<object> allObjs = CustomFiltrar != null 
                ? CustomFiltrar(Set, Context).Cast<object>() 
                : Set.Project().Cast<object>().AsEnumerable();

            foreach (var property in ListFilter.Properties)
            {
                if (property.Value != null)
                {
                    var closure = property;
                    var clrProp = closure.EntityPropertyConceptualInfo.Property;
                    if (clrProp.PropertyType == typeof (string))
                    {
                        allObjs = allObjs.Where(o =>
                        {
                            var val = clrProp.GetValue(o, null) as string;

                            return val != null && val.ToLower().Contains(((string)closure.Value).ToLower());
                        });
                    }
                    else
                    {
                        allObjs = allObjs.Where(o => closure.Value.Equals(clrProp.GetValue(o, null)));    
                    }
                    
                }
            }
            EntitiesList = allObjs.ToArray();
        }

        
        public Action<object, Action<IActionResult>> CustomModificar { get; set; }
        public Action<Action<IActionResult>> CustomInserir { get; set; }
        public Action<object, IDbContext> CustomDelete { get; set; }
        public Func<IDbSet, IDbContext, IQueryable> CustomFiltrar { get; set; }

        public ICommand Incluir
        {
            get
            {
                return new FunctionalCommand(() =>
                {
                    if (CustomInserir != null)
                    {
                        CustomInserir(result =>
                        {
                            if (result.Resultado == ResultadoDoFormulario.Salvo) ReloadEntities();
                        });
                    }
                    else
                    {
                        var model = new EntityViewerModel(_ctxFactory.Invoke(), Type, CRUDOperationType.Create,
                            readOnlyProperties: ReadOnlyProperties, hideProperties: HideProperties);
                        model.Finished += ReloadEntities;
                        new EntityViewerWindow(model).Show();
                        
                    }
                });
            }
        }
        public ICommand Modificar
        {
            get
            {
                return new FunctionalCommand(() =>
                {
                    var keys = ConceptualInfos.EntitiesConceptualInfos[Type]
                        .Keys.Select(k => k.GetValue(SelectedEntity, null)).ToArray();
                    if (CustomModificar != null)
                    {
                        CustomModificar(Set.Find(keys), r =>
                        {
                            if (r.Resultado == ResultadoDoFormulario.Salvo)
                            {
                                ReloadEntities();
                            }
                        });
                    }
                    else
                    {
                        var model = new EntityViewerModel(_ctxFactory.Invoke(), Type, CRUDOperationType.Update, keys,
                            readOnlyProperties: ReadOnlyProperties, hideProperties: HideProperties);
                        model.Finished += ReloadEntities;
                        new EntityViewerWindow(model).Show();    
                    }
                }, ()=> SelectedEntity != null);
            }
        }
        public ICommand Deletar
        {
            get
            {
                return new FunctionalCommand(() =>
                {
                    if (CustomDelete != null)
                    {
                        CustomDelete(SelectedEntity, Context);
                    }
                    else
                    {
                        Set.Remove(SelectedEntity);
                        Context.SaveChanges();    
                    }
                    
                    ReloadEntities();
                }, () => SelectedEntity != null);
            }
        }

        public ICommand LimparFiltros
        {
            get
            {
                return new FunctionalCommand(() =>
                {
                    ListFilter.Properties.ToList().ForEach(p=> p.Value = null);
                    FilterEntities();
                });
            }
        }
        public ICommand Filtrar
        {
            get
            {
                return new FunctionalCommand(() =>
                {
                    FilterEntities();
                });
            }
        }

        public bool IncluirVisivel { get; set; }
        public bool ModificarVisivel { get; set; }
        public bool DeletarVisivel { get; set; }
    }

    public class ListFilter
    {
        public EntityViewerPropertyFilter[] Properties { get; set; }

        public ListFilter(EntityViewerPropertyFilter[] properties)
        {
            Properties = properties;
        }
    }
}